/**
 * Copyright 1998-2006, CHISEL Group, University of Victoria, Victoria, BC, Canada.
 * All rights reserved.
 */
package ca.uvic.cs.cogz.mapping.ui.tree;

import java.awt.Font;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.image.BufferedImage;
import java.util.ArrayList;
import java.util.Hashtable;

import javax.swing.Icon;
import javax.swing.ImageIcon;
import javax.swing.JPanel;
import javax.swing.JTree;
import javax.swing.UIManager;
import javax.swing.tree.TreePath;

import ca.uvic.cs.cogz.util.IconConstants;
import ca.uvic.cs.cogz.util.TreeUtil;
import edu.stanford.smi.protege.model.Cls;
import edu.stanford.smi.protege.model.Frame;
import edu.stanford.smi.protege.resource.Icons;
import edu.stanford.smi.protege.util.LazyTreeNode;
import edu.stanford.smi.protegex.owl.ui.icons.OWLIcons;

/**
 * This class manages the zoom level for JTree elements. The MapRenderer class uses this manager to
 * determine the text size and icon size of its data.
 * 
 * @author Sean Falconer
 * @date 19-Feb-07
 */
public class ZoomManager {
	public static final int DEFAULT_ZOOM_FACTOR = 5;

	/** constants for the various types of scalable icons within the system */
	public static final int ICON_CLS = 0;
	public static final int ICON_CLS_METADATA = 1;
	public static final int ICON_CLS_ABSTRACT = 2;
	public static final int ICON_TREE_COLLAPSED = 3;
	public static final int ICON_TREE_EXPANDED = 4;
	public static final int ICON_MAPPED = 5;
	public static final int ICON_TEMPORARILY_MAPPED = 6;
	public static final int ICON_CANDIDATE = 7;
	public static final int ICON_PINNED = 8;
	public static final int ICON_OBJECT_SLOT = 9;
	public static final int ICON_DATATYPE_SLOT = 10;
	public static final int ICON_DEFAULT_SLOT = 11;
	public static final int ICON_INSTATIATED = 12;
	
	/** constants for zooming parameters */
	private static final float MIN_ZOOM_LEVEL = 0.2f;
	private static final float MAX_ZOOM_LEVEL = 2.0f;
	private static final float DEFAULT_ZOOM_GRANULARITY = 10.0f;
	private static final float ZOOM_INCREMENT = (MAX_ZOOM_LEVEL - MIN_ZOOM_LEVEL) / DEFAULT_ZOOM_GRANULARITY + 0.005f;
	private static final int MAX_ZOOM_ELEMENTS = (int) (MAX_ZOOM_LEVEL / ZOOM_INCREMENT);

	/** constant for the initial default font size */
	private static final float DEFAULT_FONT_SIZE = 10.0f;

	/** factor used to scale font size fall off nicely */
	private static final float DAMPENING_FACTOR = 10.0f;

	/** a static table, representing all the zoomable icon types and their zoom levels */
	private static Hashtable<Integer, ArrayList<ImageIcon>> icons = null;

	/** a list of all fonts at various zoom levels */
	private static ArrayList<Font> fonts = null;

	/** the overall zoom level for the tree */
	private float overallZoom = (MAX_ZOOM_LEVEL - MIN_ZOOM_LEVEL) / 1.8f;

	/** the previously set zoom level */
	private float prevZoom;

	/** flag to indicate whether semantic zoom (i.e. fish-eye) is enabled or not */
	private boolean semanticZoom = false;

	/** reference to the tree this zoom manager is used on */
	private JTree tree;

	public ZoomManager(Font font) {
		if (icons == null) {
			setupIcons();
		}
		if (fonts == null) {
			setupFonts(font);
		}
	}

	public void setTree(JTree tree) {
		this.tree = tree;
	}

	public JTree getTree() {
		return tree;
	}

	public void setOverallZoom(int zoom) {
		this.overallZoom = (float) zoom / (float) DEFAULT_ZOOM_FACTOR;
	}

	public void setSemanticZoom(boolean semanticZoom) {
		this.semanticZoom = semanticZoom;
	}

	public boolean isSemanticZoom() {
		return semanticZoom;
	}

	public Font getScaledFont() {
		return getScaledFont(null);
	}

	public Font getScaledFont(Object o) {
		if (o != null && semanticZoom) {
			return fonts.get(getIndex(o));
		}

		return fonts.get(getIndex());
	}

	/**
	 * Gets the scaled version of the iconType. The scaled versions are precomputed, so an index is
	 * calculated based on the current zoom level.
	 */
	public Icon getScaledIcon(int iconType) {
		return getScaledIcon(iconType, null);
	}

	/**
	 * Gets the scaled version of the iconType. The scaled versions are precomputed, so an index is
	 * calculated based on the current zoom level.
	 */
	public Icon getScaledIcon(int iconType, Object o) {
		ArrayList<ImageIcon> iconList = icons.get(iconType);

		if (iconList != null && o != null && semanticZoom) {
			return iconList.get(getIndex(o));
		} else if (iconList != null) {
			return iconList.get(getIndex());
		}
		return null;
	}

	/**
	 * Updates the UIManager's tree icons based on the current zoom levels.
	 */
	public void updateTreeIcons() {
		if (prevZoom != overallZoom) {
			UIManager.put("Tree.collapsedIcon", getScaledIcon(ICON_TREE_COLLAPSED));
			UIManager.put("Tree.expandedIcon", getScaledIcon(ICON_TREE_EXPANDED));
		}
	}

	private void setupFonts(Font font) {
		fonts = new ArrayList<Font>(MAX_ZOOM_ELEMENTS);

		for (float zoom = MIN_ZOOM_LEVEL; zoom <= MAX_ZOOM_LEVEL; zoom += ZOOM_INCREMENT) {
			fonts.add(font.deriveFont(DEFAULT_FONT_SIZE * zoom));
		}
	}

	private void setupIcons() {
		icons = new Hashtable<Integer, ArrayList<ImageIcon>>();
		icons.put(ICON_CLS, buildIconArray(Icons.getClsIcon()));
		icons.put(ICON_CLS_METADATA, buildIconArray(Icons.getClsIcon(true, false, false, false)));
		icons.put(ICON_CLS_ABSTRACT, buildIconArray(Icons.getClsIcon(false, true, false, false)));
		icons.put(ICON_TREE_COLLAPSED, buildIconArray(UIManager.getIcon("Tree.collapsedIcon")));
		icons.put(ICON_TREE_EXPANDED, buildIconArray(UIManager.getIcon("Tree.expandedIcon")));
		icons.put(ICON_MAPPED, buildIconArray(IconConstants.ICON_MAPPED));
		icons.put(ICON_TEMPORARILY_MAPPED, buildIconArray(IconConstants.ICON_TEMPORARILY_MAPPED));
		icons.put(ICON_CANDIDATE, buildIconArray(IconConstants.ICON_CANDIDATE));
		icons.put(ICON_PINNED, buildIconArray(IconConstants.ICON_PINNED_ITEM));
		icons.put(ICON_DATATYPE_SLOT, buildIconArray(OWLIcons.getImageIcon(OWLIcons.OWL_DATATYPE_PROPERTY)));
		icons.put(ICON_OBJECT_SLOT, buildIconArray(OWLIcons.getImageIcon(OWLIcons.OWL_OBJECT_PROPERTY)));
		icons.put(ICON_DEFAULT_SLOT, buildIconArray(Icons.getSlotIcon()));
		icons.put(ICON_INSTATIATED, buildIconArray(IconConstants.ICON_INSTANTIATED));
	}

	private ArrayList<ImageIcon> buildIconArray(Icon icon) {
		ArrayList<ImageIcon> iconList = new ArrayList<ImageIcon>(MAX_ZOOM_ELEMENTS);

		for (float zoom = MIN_ZOOM_LEVEL; zoom <= MAX_ZOOM_LEVEL; zoom += ZOOM_INCREMENT) {
			iconList.add(getScaledIcon(icon, zoom));
		}

		return iconList;
	}

	private int getIndex(Object o) {
		TreePath[] selectedPaths = tree.getSelectionPaths();
		ArrayList<TreePath> paths = null;
		if (selectedPaths != null) {
			if (o instanceof Cls) {
				// add object to an array
				ArrayList<Frame> frameList = new ArrayList<Frame>(1);
				frameList.add((Cls) o);

				// get all treepaths for this object
				paths = TreeUtil.getTreePaths(tree, frameList, false);
			} else if (o instanceof LazyTreeNode) {
				LazyTreeNode lazyTreeNode = (LazyTreeNode) o;
				TreePath path = TreeUtil.getTreePath(lazyTreeNode);

				paths = new ArrayList<TreePath>();
				paths.add(path);
			}

			// get the shortest distance between the object's tree paths and the selected paths
			double distance = getDistance(paths) / DAMPENING_FACTOR;

			// calculate the zoom for this object
			float zoom = Math.max(overallZoom - (float) Math.log(distance + 1), MIN_ZOOM_LEVEL);

			//System.out.println(o + " " + getDistance(paths) + " " + zoom + " " + getIndex(zoom));

			// return the index based on the zoom level
			return getIndex(zoom);
		}

		return getIndex();
	}

	private int getIndex() {
		return getIndex(overallZoom);
	}

	private int getIndex(float zoom) {
		return Math.min((int) (zoom / ZOOM_INCREMENT), MAX_ZOOM_ELEMENTS - 1);
	}

	private double getDistance(ArrayList<TreePath> paths) {
		TreePath[] selectedPaths = tree.getSelectionPaths();
		if (selectedPaths == null) {
			return 0;
		}

		double distance = Integer.MAX_VALUE;
		for (TreePath path : paths) {
			int pathRow = tree.getRowForPath(path);
			for (TreePath selectedPath : selectedPaths) {
				int selectedRow = tree.getRowForPath(selectedPath);
				distance = Math.min(distance, Math.abs(selectedRow - pathRow));
			}
		}
		return distance;
	}

	private ImageIcon getScaledIcon(Icon icon, float zoomValue) {
		BufferedImage img = new BufferedImage(icon.getIconWidth(), icon.getIconHeight(), BufferedImage.TYPE_INT_ARGB); // you can change the type as needed
		Graphics2D g = img.createGraphics();
		icon.paintIcon(new JPanel(), g, 0,0);
		
		ImageIcon scaledIcon = new ImageIcon(img);
		int width = (int) (icon.getIconWidth() * zoomValue);
		int height = (int) (icon.getIconHeight() * zoomValue);

		if (width != scaledIcon.getIconWidth() && height != scaledIcon.getIconHeight()) {
			Image image = img;
			image = image.getScaledInstance(width, height, Image.SCALE_SMOOTH);
			scaledIcon.setImage(image);
		}

		return scaledIcon;

//		ImageIcon scaledIcon = new ImageIcon(((ImageIcon) icon).getImage());
//		int width = (int) (icon.getIconWidth() * zoomValue);
//		int height = (int) (icon.getIconHeight() * zoomValue);
//
//		if (width != scaledIcon.getIconWidth() && height != scaledIcon.getIconHeight()) {
//			Image image = ((ImageIcon) icon).getImage();
//			image = image.getScaledInstance(width, height, Image.SCALE_SMOOTH);
//			scaledIcon.setImage(image);
//		}
//
//		return scaledIcon;
	}
}
